/*
 * TestJugador.cpp
 *
 *  Created on: Feb 13, 2010
 *      Author: jgrande
 */

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>

#include "ObservadorUniverso.h"

#include "Juego.h"
#include "Jugador.h"
#include "Flota.h"

#include "../../Infraestructura/Directorio.h"
#include "../../Infraestructura/DirectorioImpl.h"

#include "../../Common/CommonTypes.h"
#include "../../Common/Registry.h"
#include "../../Common/Mensajes/Encabezado.h"
#include "../../Common/Mensajes/MensajeAplicacion.h"
#include "../../Common/Mensajes/DatosCrearFlotaOrden.h"
#include "../../Common/Mensajes/DatosCambiarRumboOrden.h"
#include "../../Common/Mensajes/DatosAbandonaJugador.h"
#include "../../Common/Mensajes/MensajesCommonTypes.h"

#include "../../../Framework/IO/log.h"

#include "../../../Framework/Shared/shmap.h"

#include "../../../Middleware/Comunicaciones/MiddlewareCliente.h"
#include "../../../Middleware/Comunicaciones/TraductorDirecciones.h"

#include "../../../Middleware/Common/IdFlota.h"
#include "../../../Middleware/Common/IdPlaneta.h"

using aplicacion::InformacionCreacionUniverso;
using aplicacion::JuegoDTO;
using aplicacion::RegistrySingleton;
using aplicacion::t_listaJuegos;
using aplicacion::t_listaUniversos;
using aplicacion::t_listaPlanetas;
using aplicacion::Directorio;
using aplicacion::DirectorioImpl;
using aplicacion::Encabezado;
using aplicacion::MensajeAplicacion;
using aplicacion::DatosCrearFlotaOrden;
using aplicacion::DatosCambiarRumboOrden;
using aplicacion::DatosAbandonaJugador;

using aplicacion::jugador::Jugador;
using aplicacion::jugador::Juego;
using aplicacion::jugador::Flota;

using namespace framework::io;

using middleware::comunicaciones::TraductorDireccionesSingleton;

#define MAX_N_STREAM 1000

static MapaFlotas* gFlotas;
static Juego* gJuego;
static Jugador* gJugador;
static bool terminar = false;
static pid_t pidObservador = (pid_t)-1;

void jugador_sigint_handler(int signal) {
	logger << LogType::LOG_DEBUG_0 << "apagando middleware" << logger.endl;
	MiddlewareClienteSingleton::instance().finish();
	exit(0);
}

void termino_observador(int signal) {
	logger << LogType::LOG_DEBUG_0 << "Se recibió señal de fin de observador" << logger.endl;
	terminar = true;
}

void abandonarJuego() {
	logger << "Avisando a los planetas que el jugador local va a abandonar el "
			"juego..." << logger.endl;

	DatosAbandonaJugador* datos = new DatosAbandonaJugador();
	MensajeAplicacion msj;
	msj.getEncabezado().setTipo(ABANDONA_JUGADOR);
	msj.getEncabezado().setOrigen(gJugador->getDTO().idJugador);
	msj.setDatos(datos);

	Directorio& directorio = RegistrySingleton::instance().getDirectorio();
	t_listaPlanetas planetas = directorio.listarPlanetas(gJuego->getIdUniverso());
	t_listaPlanetas::iterator it = planetas.begin();

	for(;it!=planetas.end();it++) {
		msj.getEncabezado().setDestino(it->_identificador);

		Direccion dest = TraductorDireccionesSingleton::instance().getDireccion(
				it->_identificador);
		if(dest==DIR_INVALIDA) {
			logger << LogType::LOG_ERROR << "Error: no se encontró la dirección "
					"del planeta " << it->_identificador << logger.endl;
		} else {
			logger << LogType::LOG_DEBUG_0 << "\tavisando al planeta " <<
					it->_identificador << logger.endl;
			MiddlewareClienteSingleton::instance().send(msj, dest);
		}
	}

	if(pidObservador!=(pid_t)-1) {
		kill(pidObservador, SIGINT);
	}
	terminar = true;
}

Universo* seleccionarUniverso() {
	Directorio& dir = RegistrySingleton::instance().getDirectorio();
	t_listaUniversos universos = dir.listarUniversosCerrados();
	Universo* sel = NULL;
	t_listaUniversos::size_type i=1;
	t_listaUniversos::size_type nSel;

	for(t_listaUniversos::iterator it=universos.begin();it!=universos.end();it++) {
		std::cout << i++ << ": " << *it << "\n";
	}

	logger << "Seleccione el universo al que desea asociar el juego: " << logger.endl;
	std::cin >> nSel;

	if(nSel > 0 && nSel < i) {
		t_listaUniversos::iterator it = universos.begin();
		for(i=1;i<nSel;i++, it++);
		sel = new Universo(*it);
	}

	if(sel==NULL) {
		logger << "error: número de universo inválido" << logger.endl;
	}

	return sel;
}

Juego* crearJuego() {
	Directorio& dir = RegistrySingleton::instance().getDirectorio();
	Universo* universo = NULL;
	JuegoDTO dto;

	if(!(universo=seleccionarUniverso())) {
		return NULL;
	}

	dto.idUniverso = universo->getId();

	logger << "Ingrese cantidad de jugadores: " << logger.endl;
	std::cin >> dto.min;

	// en el prototipo queda fijo que la cantidad de jugadores no puede
	// cambiarse a lo largo del juego
	dto.max = dto.min;

	if(!dir.registrarNuevo(dto)) {
		logger << "error creando juego!" << logger.endl;
		return NULL;
	}

	return new Juego(dto);
}

/**
 * Imprime una lista con todos los juegos disponibles. Devuelve un puntero
 * al juego seleccionado por el usuario. Devuelve NULL si el número de juego
 * selecciona por el usuario es inválido.
 */
Juego* seleccionarJuego() {
	Directorio& dir = RegistrySingleton::instance().getDirectorio();
	t_listaJuegos juegos = dir.listarJuegosAbiertos();
	Juego* sel = NULL;
	t_listaJuegos::size_type i=1;
	t_listaJuegos::size_type nSel;

	for(t_listaJuegos::iterator it=juegos.begin();it!=juegos.end();it++) {
		std::cout << i++ << ": " << *it << "\n";
	}

	logger << "Ingrese el número de juego al que desea incorporarse: " << logger.endl;
	std::cin >> nSel;

	if(nSel > 0 && nSel < i) {
		t_listaJuegos::iterator it = juegos.begin();
		for(i=1;i<nSel;i++, it++);
		sel = new Juego(*it);
	}

	if(sel==NULL) {
		logger << "error: número de juego inválido" << logger.endl;
	}

	return sel;
}

Juego* seleccionarOCrearJuego() {
	char opcion;

	do {
		logger << "¿Desea (e)legir un juego existente, (c)rear un juego nuevo, o (s)alir?" << logger.endl;
		std::cin >> opcion;
	} while(opcion!='s' && opcion!='c' && opcion!='e');

	switch(opcion) {
	case 'e': return seleccionarJuego();
	case 'c': return crearJuego();
	default: return NULL;
	}
}

void crearFlota() {
	static T_ID_FLOTA nextFlotaId = 1;
	T_ID_PLANETA idPlanetaOrigen, idPlanetaDestino;
	double proporcion;

	std::cin >> idPlanetaOrigen;
	if(idPlanetaOrigen==0) {
		return;
	}

	std::cin >> idPlanetaDestino;
	if(idPlanetaDestino==0) {
		return;
	}

	std::cin >> proporcion;
	if(proporcion==0) {
		return;
	}

	IdPlaneta origen(idPlanetaOrigen, gJuego->getIdUniverso());
	Direccion dir = TraductorDireccionesSingleton::instance().getDireccion(
			origen);
	IdPlaneta destino(idPlanetaDestino, gJuego->getIdUniverso());

	if(dir==DIR_INVALIDA) {
		logger << "Error! Id de planeta inválido!" << logger.endl;
		return;
	}

	Encabezado encabezado;
	encabezado.setTipo(JUGADOR_CREAR_FLOTA);
	encabezado.setOrigen(gJugador->getDTO().idJugador);
	encabezado.setDestino(origen);

	DatosCrearFlotaOrden *datos = new DatosCrearFlotaOrden();
	datos->_idFlota = nextFlotaId++;
	datos->_planetaDestino = destino;
	datos->_proporcionNaves = proporcion;
	datos->_semilla = rand();

	MensajeAplicacion msj(encabezado, datos);
	MiddlewareClienteSingleton::instance().send(msj, dir);
}

void cambiarFlota() {
	T_ID_FLOTA idFlota;
	T_ID_PLANETA idPlanetaNuevo;

	std::cin >> idFlota;
	if(idFlota==0) {
		return;
	}

	std::cin >> idPlanetaNuevo;
	if(idPlanetaNuevo==0) {
		return;
	}

	// busca el destino actual de la flota
	gFlotas->lock();
	if(!gFlotas->contains(idFlota)) {
		gFlotas->unlock();
		logger << "Error: id de flota desconocido!" << logger.endl;
		return;
	}
	Flota f = gFlotas->get(idFlota);
	gFlotas->unlock();

	// traduce el id del destino anterior a dirección del middleware
	Direccion dirAnterior = TraductorDireccionesSingleton::instance().getDireccion(
					f.getDestino());
	if(dirAnterior==DIR_INVALIDA) {
		logger << "Error: id de planeta destino anterior desconocido!" << logger.endl;
		return;
	}

	// genera el encabezado del mensaje de cambio de rumbo
	// el destino del mensaje es el destino anterior
	Encabezado encabezado;
	encabezado.setTipo(JUGADOR_CAMBIO_RUMBO_FLOTA);
	encabezado.setOrigen(gJugador->getDTO().idJugador);
	encabezado.setDestino(f.getDestino());

	// genera los datos del mensaje
	DatosCambiarRumboOrden *datos = new DatosCambiarRumboOrden();
	datos->_idFlota = idFlota;
	datos->_idDestinoNuevo = IdPlaneta(idPlanetaNuevo, gJuego->getIdUniverso());

	// envía el mensaje
	MensajeAplicacion msj(encabezado, datos);
	MiddlewareClienteSingleton::instance().send(msj, dirAnterior);
}

void ayuda() {
	const char* ayuda =
			"\n"
			"USO: debe ingresar uno de los siguientes comandos\n"
			"\n"
			"	cr <idPlanetaOrigen> <idPlanetaDestino> <proporcion>\n"
			"		crea una flota con destino <idPlaneta> y una cantidad de\n"
			"		naves igual a <proporcion> por la cantidad de naves que\n"
			"		hay en el planeta. <proporcion> debe ser un valor mayor\n"
			"		o igual a cero y menor a uno.\n"
			"\n"
			"	ca <idFlota> <idPlaneta>\n"
			"		cambia el rumbo de la flota <idFlota> poniendo como nuevo\n"
			"		destino el planeta <idPlaneta>.\n";
	logger << ayuda << logger.endl;
}

void mainLoop() {
	forever {
		if(terminar) {
			break;
		}
		std::string cmd;
		std::cin >> cmd;
		if(terminar) {
			break;
		}
		if(cmd=="help") {
			logger << "imprimir la ayuda" << logger.endl;
			ayuda();
		} else if(cmd=="cr") {
			logger << "comando: crear flota" << logger.endl;
			crearFlota();
		} else if(cmd=="ca") {
			logger << "comando: cambiar rumbo de flota" << logger.endl;
			cambiarFlota();
		} else if(cmd=="sa") {
			logger << "comando: salir" << logger.endl;
			abandonarJuego();
		} else {
			logger << "comando desconocido" << logger.endl;
		}
		std::cin.ignore(MAX_N_STREAM, '\n');
	}

	logger << "juego terminado!" << logger.endl;
}

int main(int argc, char* argv[]) {
	logger.set_pname("Jugador");
	//Se define el nivel de logueo deseado
	LogConfiguracionSingleton::instance().nivelLogueo = LogType::LOG_INFO;
	srand((unsigned int)time(NULL));

	MapaFlotas flotas;
	if(!flotas) {
		logger << "Error! No se pudo crear el mapa compartido de flotas"
				<< logger.endl;
		return 1;
	}

	if(!MiddlewareClienteSingleton::instance().start()) {
		logger << "Error! No se pudo inicializar middleware" << logger.endl;
		return 2;
	}

	Utiles::registrar_signal(SIGUSR1, termino_observador);
	Utiles::registrar_signal(SIGINT, jugador_sigint_handler);

	Directorio* dir = new DirectorioImpl();
	RegistrySingleton::instance().setDirectorio(dir);
	Juego* juego = seleccionarOCrearJuego();
	Jugador jugador("Juan Grande");

	if(juego) {
		logger << logger.underline << juego->getDTO() << logger.nounderline << " seleccionado" << logger.endl;
		if(juego->entrar(jugador)) {
			gFlotas = &flotas;
			gJuego = juego;
			gJugador = &jugador;
			pidObservador = lanzarObservadorUniverso(&flotas, &jugador, juego->getIdUniverso());
			if(pidObservador!=(pid_t)-1) {	
				mainLoop();
	
				logger << LogType::LOG_DEBUG_0 << "esperando finalización de observador de universo..." << logger.endl;
				while(waitpid(pidObservador, NULL, 0)!=pidObservador);

				RegistrySingleton::instance().getDirectorio().remove(gJugador->getDTO());
			} else {
				logger << LogType::LOG_ERROR << "error lanzando observador de universo" << logger.endl;
			}
		}
		delete juego;
	}

	logger << LogType::LOG_DEBUG_0 << "apagando middleware" << logger.endl;
	MiddlewareClienteSingleton::instance().finish();

	return 0;
}
